﻿using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Threading.Tasks;

namespace PerfAnalyzerLib
{
	public class PerfLogManager
	{
		static private Object _parsingLock = new object();
		static private List<PerfLog> _perflogs = new List<PerfLog>();
		static private DateTime _parseStart;
		static public TimeSpan _parseDuration;

		#region Event - End of parsing
		public delegate void PerflogManagerParsingCompletedEventHandler(object sender, EventArgs e);
		static public event PerflogManagerParsingCompletedEventHandler ParsingCompleted;

		static void OnPerflogManagerParsingCompletion(EventArgs e)
		{
			if (ParsingCompleted != null)
			{
				ParsingCompleted(null, e);
			}
		}
		#endregion

		#region Event ParsingUpdate
		public delegate void PerflogManagerParsingUpdateEventHandler(object sender, PerflogManagerParsingEventArgs e);
		public static event PerflogManagerParsingUpdateEventHandler ParsingStatusUpdate;

		private static void OnPerflogManagerParsingStatusUpdate(PerfLog log, PerflogManagerParsingEventArgs e)
		{
			PerflogManagerParsingUpdateEventHandler handler = ParsingStatusUpdate;
			if (handler != null) handler(log, e);
		}

		private static void OnPerflogParsingUpdated(object sender, PerflobParsingStatusEventArgs e)
		{
			var args = new PerflogManagerParsingEventArgs(e.ParsingPercentage, false);
			OnPerflogManagerParsingStatusUpdate((PerfLog)sender, args);
		}

		private static void OnPerflogParsingCompleted(PerfLog sender)
		{
			var args = new PerflogManagerParsingEventArgs(100, true);
			OnPerflogManagerParsingStatusUpdate(sender, args);
		}

		public class PerflogManagerParsingEventArgs : EventArgs
		{
			public PerflogManagerParsingEventArgs(double percentParsed, bool parsed)
			{
				PercentParsed = percentParsed;
				Parsed = parsed;
			}

			public volatile bool Parsed = false;
			public double PercentParsed;
		}
		#endregion

		/// <summary>
		/// List of perflogs to manage.
		/// </summary>
		static public List<PerfLog> PerfLogs
		{
			get { return _perflogs; }
		}

		/// <summary>
		/// Duration of the last parse (if succeeded).
		/// </summary>
		static public TimeSpan ParseDuration
		{
			get { return _parseDuration; }
		}

		/// <summary>
		/// List of state infos to track duriong parsing.
		/// </summary>
		static private List<PerflogManagerParsingStateInfo> _stateInfos = new List<PerflogManagerParsingStateInfo>();

		/// <summary>
		/// Add the given perflog in the list of perflogs to manage.
		/// </summary>
		/// <param name="filename">File to add.</param>
		static public PerfLog AddPerfLog(string filename)
		{
			// Check for null or empty
			if (string.IsNullOrWhiteSpace(filename))
			{
				throw new ArgumentException("Invalid filename, it cannot be null or empty.");
			}

			// Check if file exists
			if (!File.Exists(filename))
			{
				throw new FileNotFoundException("Could not find the file: " + filename, filename);
			}

			// if the file is already in the list, simply ignore it.
			// TODO: rethink if returning null is the right decision.
			if (IsFileInList(filename))
			{
				return null;
			}

			PerfLog newPerflog = new PerfLog(filename);
			_perflogs.Add(newPerflog);
			return newPerflog;
		}

		/// <summary>
		/// Parse all the perflogs contained in the list.
		/// </summary>
		static public void Parse()
		{
			_parseDuration = TimeSpan.Zero;
			_parseStart = DateTime.Now;
			ThreadPool.SetMaxThreads(4, 4);

			foreach (var perfLog in PerfLogs)
			{
				var stateInfo = new PerflogManagerParsingStateInfo();
				_stateInfos.Add(stateInfo);
				stateInfo.Perflog = perfLog;
				ThreadPool.QueueUserWorkItem(new WaitCallback(WorkerThread), stateInfo);
			}
		}

		static public async Task ParseAsync()
		{
			var tasks = new List<Task>();

			foreach (var perfLog in PerfLogs)
			{
				tasks.Add(Task.Factory.StartNew(() => { perfLog.Parse(); }));
			}

			_parseDuration = TimeSpan.Zero;
			_parseStart = DateTime.Now;

			Task.WaitAll(tasks.ToArray());

				//foreach (var perfLog in PerfLogs)
				//{
				//	var stateInfo = new PerflogManagerParsingStateInfo();
				//	_stateInfos.Add(stateInfo);
				//	stateInfo.Perflog = perfLog;
				//	ThreadPool.QueueUserWorkItem(new WaitCallback(WorkerThread), stateInfo);
				//}

			return;
		}

		/// <summary>
		/// Function to be called by worker thread for parsing.
		/// </summary>
		/// <param name="stateInfo">Information related to parsing.</param>
		static private void WorkerThread(Object stateInfo)
		{
			var state = (PerflogManagerParsingStateInfo)stateInfo;

			var eventHandler = new PerfLog.ParsingStatusEventHandler(OnPerflogParsingUpdated);
			state.Perflog.ParsingStatusUpdate += eventHandler;

			state.Perflog.Parse();

			OnPerflogParsingCompleted(state.Perflog);
			state.Perflog.ParsingStatusUpdate -= eventHandler;

			state.Parsed = true;

			lock (_parsingLock)
			{
				foreach (var sInfo in _stateInfos)
				{
					if (!sInfo.Parsed)
					{
						return;
					}
				}
			}

			_stateInfos.Clear();
			OnPerflogManagerParsingCompletion(EventArgs.Empty);

			// TODO what to do when stoping parsing?
			_parseDuration = DateTime.Now - _parseStart;
		}

		/// <summary>
		/// Check if the given file is already in the perflog list.
		/// </summary>
		/// <param name="filename">File to check.</param>
		/// <returns>True if the file is already in the perflog list.</returns>
		static public bool IsFileInList(string filename)
		{
			foreach (PerfLog pl in PerfLogs)
			{
				if (pl.Filename == filename)
				{
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Remove all the perflogs in the list.
		/// </summary>
		static public void ClearPerflogList()
		{
			_perflogs = new List<PerfLog>();
		}

		/// <summary>
		/// Remove given perflog from the list.
		/// </summary>
		/// <param name="perflog">Perflog to be removed.</param>
		static public void RemovePerlog(PerfLog perflog)
		{
			if (_perflogs.Contains(perflog))
			{
				_perflogs.Remove(perflog);
			}
		}

		/// <summary>
		/// Export counter info and stats to the given file.
		/// </summary>
		/// <param name="filename">File to export the counter info to.</param>
		static public void Export(string filename)
		{
			// perflog, counter, avg, min, max, 25th, 50th, 75th, 90th, 95th, 99th

			var sb = new StringBuilder();

			sb.AppendFormat("Perflog,Machine,Counter,Average,Min,Max,25th Percentile,50th Percentile,75th Percentile,90th Percentile,95th Percentile,99th Percentile\r\n");

			// TODO deal with empty list when export is called (exception?).
			// TODO what to do when export is called before parse is called?
			if (0 == _perflogs.Count)
			{
				return;
			}

			foreach (var file in _perflogs)
			{
				sb.Append(file.Export());
			}

			if (File.Exists(filename))
			{
				File.Delete(filename);
			}

			File.AppendAllText(filename, sb.ToString());
		}


		static public void Export(string filename, IEnumerable<PerfLog> logs)
		{
			// perflog, counter, avg, min, max, 25th, 50th, 75th, 90th, 95th, 99th

			var sb = new StringBuilder();

			sb.AppendFormat("Perflog,Machine,Counter,Average,Min,Max,25th Percentile,50th Percentile,75th Percentile,90th Percentile,95th Percentile,99th Percentile\r\n");

			// TODO deal with empty list when export is called (exception?).
			// TODO what to do when export is called before parse is called?
			if (0 == logs.Count())
			{
				return;
			}

			foreach (var file in logs)
			{
				sb.Append(file.Export());
			}

			if (File.Exists(filename))
			{
				File.Delete(filename);
			}

			File.AppendAllText(filename, sb.ToString());
		}
	}
}
